/**
 * ExtPixel.java
 * 
 * @date: 2012-02-13
 * @author: Xiaoyu Guo (MUN# 200982155)
 * This file is part of the course project for COMP 6752, 
 * Application of Computer Graphics. All the file is written
 * by myself.
 */
package mun.cs6752.project.model;

import java.awt.image.BufferedImage;

/** Extended Pixel defines pixel-level operations including color information 
 * extraction, conversion between color models, etc.
 * @author Xiaoyu Guo
 */
public class ExtPixel {
    private static final int COLOR_RANGE_MAX = 0xFF;
    private static final int COLOR_RANGE_MIN = 0;
    
    private int r;
    private int g;
    private int b;
    private int a;
    
    protected ExtPixel(){}
    
    /** Construction using existing information
     * @param copy
     */
    public ExtPixel(ExtPixel copy){
        if(copy == null){
            throw new IllegalArgumentException("can not copy from null");
        }
        this.r = copy.r;
        this.g = copy.g;
        this.b = copy.b;
        this.a = copy.a;
    }
    
    /** Construction using specified R, G, B and A
     * @param r
     * @param g
     * @param b
     * @param a
     */
    public ExtPixel(int r, int g, int b, int a){
        setR(r);
        setG(g);
        setB(b);
        setA(a);
    }
    
    /** Construction using specified R, G, B and set A = maximum 
     * @param r
     * @param g
     * @param b
     */
    public ExtPixel(int r, int g, int b){
        setR(r);
        setG(g);
        setB(b);
        setA(COLOR_RANGE_MAX);
    }
    
    /** Get ExtPixel from specified image
     * @param img
     * @param x
     * @param y
     * @return
     */
    public static final ExtPixel get(BufferedImage img, int x, int y){
        if(img == null){
            throw new IllegalArgumentException("image is null");
        }
        int rgb = img.getRGB(x, y);
        return ExtPixel.fromRGB(rgb);
    }
    
    /** Get ExtPixel from integer RGB value
     * @param rgb
     * @return
     */
    public static final ExtPixel fromRGB(int rgb){
        ExtPixel result = new ExtPixel();
        result.setA((rgb & 0xFF000000) >> 24);
        result.setR((rgb & 0x00FF0000) >> 16);
        result.setG((rgb & 0x0000FF00) >> 8);
        result.setB(rgb & 0x000000FF);
        return result;
    }
    
    /** Get ExtPixel from given YUV color 
     * @param y
     * @param u
     * @param v
     * @return
     */
    public static final ExtPixel fromYUV(float y, float u, float v){
        return new ExtPixel(
            Math.round(y + 1.13983F * v),
            Math.round(y - 0.39465F * u - 0.58060F * v),
            Math.round(y + 2.03211F * u)
        );
    }
    
    /** Get ExtPixel from given YIQ color
     * @param y
     * @param i
     * @param q
     * @return
     */
    public static final ExtPixel fromYIQ(float y, float i, float q){
        return new ExtPixel(
            Math.round(y + 0.9563F * i + 0.6210F * q),
            Math.round(y - 0.2721F * i - 0.6474F * q),
            Math.round(y - 1.1070F * i + 1.7046F * q)
        );
    }
    
    public int getR() {
        return r;
    }
    public void setR(int r) {
        this.r = r;
    }
    public int getG() {
        return g;
    }
    public void setG(int g) {
        this.g = g;
    }
    public int getB() {
        return b;
    }
    public void setB(int b) {
        this.b = b;
    }
    public int getA() {
        return a;
    }
    public void setA(int a) {
        this.a = a;
    }
    
    public float getY(){
        return 0.299F * getR() + 0.587F * getG() + 0.114F * getB();
    }
    
    public float getI(){
        return 0.595716F * getR() - 0.274453F * getG() - 0.321263F * getB();
    }
    
    public float getQ(){
        return 0.211456F * getR() - 0.522591F * getG() + 0.31135F * getB();
    }
    
    public float getU(){
        return -0.14713F * getR() - 0.28886F * getG() + 0.436F * getB();
    }
    
    public float getV(){
        return 0.615F * getR() - 0.51499F * getG() - 0.10001F * getB();
    }
    
    /** Addition like vectors, but with cap of COLOR_RANGE_MAX
     * e.g. 215 + 128 = 255
     * @param value
     * @return
     */
    public ExtPixel add(ExtPixel value){
        return new ExtPixel(
            Math.max(COLOR_RANGE_MAX, getR() + value.getR()),
            Math.max(COLOR_RANGE_MAX, getG() + value.getG()),
            Math.max(COLOR_RANGE_MAX, getB() + value.getB()),
            Math.max(COLOR_RANGE_MAX, getA() + value.getA())
        );
    }
    
    /** Perform single-level thresholding on pixel
     * @param value
     * @return
     */
    public ExtPixel threshold(int value){
        return new ExtPixel(
            getR() > value ? COLOR_RANGE_MAX : COLOR_RANGE_MIN,
            getG() > value ? COLOR_RANGE_MAX : COLOR_RANGE_MIN,
            getB() > value ? COLOR_RANGE_MAX : COLOR_RANGE_MIN,
            getA()
        );
    }
    
    /** Perform multiple-level thresholding on pixel
     * @param value an instance of ExtPixel containing three threshold values for R, G and B 
     * @return
     */
    public ExtPixel threshold(ExtPixel value){
        return new ExtPixel(
            getR() > value.getR() ? COLOR_RANGE_MAX : COLOR_RANGE_MIN,
            getG() > value.getG() ? COLOR_RANGE_MAX : COLOR_RANGE_MIN,
            getB() > value.getB() ? COLOR_RANGE_MAX : COLOR_RANGE_MIN,
            getA()
        );
    }
    
    /** Perform alpha blending, using non-pre-multiplied pixels
     * @param value
     * @return
     */
    public ExtPixel blend(ExtPixel value){
        int one_vA = COLOR_RANGE_MAX - value.getA();
        return new ExtPixel(
            (getR() * getA() * one_vA + value.getR() * value.getA()) / COLOR_RANGE_MAX,
            (getG() * getA() * one_vA + value.getG() * value.getA()) / COLOR_RANGE_MAX,
            (getB() * getA() * one_vA + value.getB() * value.getA()) / COLOR_RANGE_MAX,
            (getA() * one_vA + value.getA()) / COLOR_RANGE_MAX
        );
    }
}
